我們接下來會使用 PHPConf 2016 的簡報「使用 Slim 為 Legacy Code 重構」提到的 proxy pattern 方法來重構。而中間的 Route 會使用 Laravel 。只是在使用 Laravel 之前,我們得先要升級 PHP 。
昨天是發現它沒有內建函式 mysql_*
,後面的版本都建議換用 mysqli_*
了。
既然如此,我們就寫一堆 mysql_*
函式,轉接到 mysqli_*
函式就好了呀!
這也是 Adapter Pattern 的應用方法之一,只是這個狀況下,我們更改原始碼的範圍不會很大。
觀查一下程式碼,原本的主要路由 admin.php
與 index.php
背後都會引用 config.php
,我們寫一個 workaround.php
在 config.php
裡載入即可。
先來試試連線函式:
if (!function_exists('mysql_connect')) {
function mysql_connect($host, $user, $pass)
{
return mysqli_connect($host, $user, $pass);
}
}
config.php
載入:
require_once __DIR__ . '/workaround.php';
接著啟動服務:
docker run --rm -it --link mysql -v `pwd`:/source -w /source -p 8080:8080 php:7.2-alpine php -S 0.0.0.0:8080
發現居然沒有 mysqli
,上網可以查得到它有支援,所以看樣子是 alpine 預設沒安裝,來換個策略:先 sh
進去安裝後,再開伺服器:
docker run --rm -it --link some-mysql:mysql -v `pwd`:/source -w /source -p 8080:8080 php:7.2-alpine sh
# 在 Docker 裡
docker-php-ext-install mysqli
php -S 0.0.0.0:8080
哦哦哦!這次又出現不一樣的訊息了:
Warning: Use of undefined constant MYSQL_ASSOC - assumed 'MYSQL_ASSOC' (this will throw an Error in a future version of PHP) in /source/class/mysql.class.php on line 67
Fatal error: Uncaught Error: Call to undefined function mysql_query() in /source/class/mysql.class.php:68 Stack trace: #0 /source/class/mysql.class.php(41): db->init() #1 /source/class/shop.class.php(56): db->__construct(true) #2 /source/index.php(8): shop->__construct(true) #3 {main} thrown in /source/class/mysql.class.php on line 68
所以看起來連線是可行的,我們只要把所有函式都接上去就行了。
經過一連串的 try & error 後,最後 workaround.php
的長相如下:
class Workaround
{
public static $mysqli;
}
define('MYSQL_ASSOC', MYSQLI_ASSOC);
if (!function_exists('mysql_connect')) {
function mysql_connect($host, $user, $pass)
{
return Workaround::$mysqli = mysqli_connect($host, $user, $pass);
}
}
if (!function_exists('mysql_close')) {
function mysql_close($link)
{
return mysqli_close($link);
}
}
if (!function_exists('mysql_query')) {
function mysql_query($query, $link = null)
{
if (null === $link) {
return mysqli_query(Workaround::$mysqli, $query);
} else {
return mysqli_query($link, $query);
}
}
}
if (!function_exists('mysql_select_db')) {
function mysql_select_db($dbname, $link)
{
return mysqli_select_db($link, $dbname);
}
}
if (!function_exists('mysql_fetch_array')) {
function mysql_fetch_array($result, $type)
{
return mysqli_fetch_array($result, $type);
}
}
if (!function_exists('mysql_num_rows')) {
function mysql_num_rows($result)
{
return mysqli_num_rows($result);
}
}
if (!function_exists('mysql_real_escape_string')) {
function mysql_real_escape_string($string, $link)
{
if (null === $link) {
return mysqli_real_escape_string(Workaround::$mysqli, $string);
} else {
return mysqli_real_escape_string($link, $string);
}
}
}
if (!function_exists('mysql_errno')) {
function mysql_errno($link = null)
{
if (null === $link) {
return mysqli_errno(Workaround::$mysqli);
} else {
return mysqli_errno($link);
}
}
}
if (!function_exists('mysql_error')) {
function mysql_error($link = null)
{
if (null === $link) {
return mysqli_error(Workaround::$mysqli);
} else {
return mysqli_error($link);
}
}
}
會有 Workaround
class 的目的是為了暫存 mysqli
連線變數。且也在 function 先做好手腳了,這樣在之後調整程式碼會比較簡單一點。
原始碼只有修改兩個地方,一個是 config.php
的引用,另一個是 mysql.class.php
有個地方把字串當陣列在用, PHP 7.2 不支援,因此只能修改了。
基本上,上面這樣就算完成了,未來就可以使用 PHP 7.2 開發了。
筆者目前有遇過的: 5.3 to 5.6 下面這個狀況會報錯:
foo(&$bar);
function foo($bar) {
}
要改成下面這樣
foo($bar);
function foo(&$bar) {
}
其他就沒有遇過了, PHP 算是相容性做很好的語言。但跟大多數語言和框架一樣,升級還是無法確定一切正常,只能靠亂點測試的運氣。更好的方法是寫自動化測試,在升級的時候跑一輪即可。
只是一個長久未重構的既有程式碼(legacy code),無法簡單地寫自動化測試,只能先使用硬上的方法讓程式變得比較好測之後,再開始把測試一個一個補上去。
程式碼可參考 GitHub PR
我剛從 php5 升級到 php8,有一個舊系統都使用 mysql 開頭的函式,看到這篇真的是幫上大忙了,非常感謝。
這麼久以前寫文章有幫上忙真的是太棒了XD